using System;
using System.Text;
using System.Windows.Controls;
using System.Windows.Documents;
using com.bodurov.SilverlightControls.XmlCodeEditor.Utils;

namespace com.bodurov.SilverlightControls.XmlCodeEditor.Extensions
{
    public static class TextPointer_Ext
    {
        public static bool OverlapsWith(this TextPointer pointer, TextElement element)
        {
            if (element == null) return false;
            return (element.ElementStart.CompareTo(pointer) + element.ElementEnd.CompareTo(pointer)) == 0;
        }


        public static int GetOffsetFrom(this TextPointer current, TextPointer targetPointer)
        {
            var sign = current.CompareTo(targetPointer);
            if (sign == 0) return 0;
            var logicalDirection = sign > 0 ? LogicalDirection.Forward : LogicalDirection.Backward;
            var i = 0;
            while (true)
            {
                current = current.GetPositionAtOffset(1 * sign, logicalDirection);
                if (current == null)
                {
                    break;
                }
                i = (i + 1 * sign);
                if (current.CompareTo(targetPointer) != sign)
                {
                    break;
                }
            }
            return i * sign;
        }

        internal struct GetPointerAtCharacterOffset_State
        {
            internal TextPointer Current;
            internal TextPointer Previous;
            internal int Offset;
            internal int Count;
            internal int Sign;
        }

        public static TextPointer GetPointerAtCharacterOffset(this TextPointer original, int offsetCharacters)
        {
            if (offsetCharacters == 0) return original;

            var state = new GetPointerAtCharacterOffset_State { Current = original, Offset = offsetCharacters };

            state = original.FoldCharacters(state,
                                            s => s.Offset > 0 ? -1 : s.Offset == 0 ? 0 : 1, // define direction (read only in regards to state)
                                            (sign, s) => // initialize (read write in regards to state)
                                            {
                                                s.Sign = sign;
                                                return s;
                                            },
                                            (sign, dir, s, curr) => curr.GetPositionAtOffset(sign * -1, dir), // go next (read only in regards to state)
                                            (tp, isNewLine, s) => // process character (read write in regards to state)
                                            {
                                                s.Count = (s.Count + -1 * s.Sign);
                                                if (tp == null) s.Previous = s.Current;
                                                s.Current = tp;
                                                return s;
                                            },
                                            (tp, s) => (s.Count == s.Offset)); // return condition (read only in regards to state)

            return state.Current ?? state.Previous ?? original;
        }

        internal struct GetCharacterOffsetFrom_State
        {
            internal int Count;
            internal int Sign;
            internal TextPointer Current;
            internal TextPointer Target;
        }

        public static int GetCharacterOffsetFrom(this TextPointer current, TextPointer targetPointer)
        {
            if (current.CompareTo(targetPointer) == 0) return 0;

            var state = new GetCharacterOffsetFrom_State { Current = current, Target = targetPointer };

            state = current.FoldCharacters(state,
                                            s => s.Current.CompareTo(s.Target), // define direction (read only in regards to state)
                                            (sign, s) => // initialize (read write in regards to state)
                                            {
                                                s.Sign = sign;
                                                return s;
                                            },
                                            (sign, dir, s, curr) => curr.GetPositionAtOffset(sign * -1, dir), // go next (read only in regards to state)
                                            (tp, isNewLine, s) => // process character (read write in regards to state)
                                            {
                                                s.Count++;
                                                s.Current = tp;
                                                return s;
                                            },
                                            (tp, s) => (tp.CompareTo(s.Target) != s.Sign)); // return condition (read only in regards to state)

            return state.Count * state.Sign * -1;
        }
        /// <summary>
        /// the processChar function parameters are:
        /// 1. Current text pointer
        /// 2. Flag indicating if this is new line between blocks (paragraphs)
        /// 3. state
        /// </summary>
        internal static T FoldCharacters<T>(this TextPointer current, T state, Func<T, int> defineDirection, Func<int, T, T> init, Func<int, LogicalDirection, T, TextPointer, TextPointer> goNext, Func<TextPointer, bool, T, T> processChar, Func<TextPointer, T, bool> returnCondition)
        {
            var sign = defineDirection(state);
            state = init(sign, state);
            if (returnCondition(current, state)) return state;
            var isFirstRow = true;
            var logicalDirection = sign < 0 ? LogicalDirection.Forward : LogicalDirection.Backward;
            var blockMaskAndStatus = new PairStruct<int, bool>();
            //StringBuilder sb = null;
            //if(Dbg.IsVerboseTrace)
            //{
            //    sb = new StringBuilder();
            //    sb.AppendLine(current.GetDescription()); 
            //}

            while (true)
            {
                var runBeforeMove = current.Parent as Run;
                if (runBeforeMove == null)
                {
                    if (isFirstRow)
                    {
                        isFirstRow = false;
                    }
                    else
                    {
                        blockMaskAndStatus = TryRecognizeNewLine(blockMaskAndStatus, current);
                        if (blockMaskAndStatus.Second)
                        {
                            //if (returnCondition(current, state)) break;
                            state = processChar(current, true, state);
                            if (returnCondition(current, state)) break;
                        }
                    }
                }
                else blockMaskAndStatus.First = 0;// reset the mask block.ContentStart must follow block.ContentEnd without meeting Run in between
                current = goNext(sign, logicalDirection, state, current);
                if (current == null)
                {
                    break;
                }
                //if (Dbg.IsVerboseTrace)
                //{
                //    sb.AppendLine(current.GetDescription());
                //}
                var runAfterMove = current.Parent as Run;
                if (runAfterMove == null)
                {
                    blockMaskAndStatus = TryRecognizeNewLine(blockMaskAndStatus, current);
                    if (blockMaskAndStatus.Second)
                    {
                        //if (returnCondition(current, state)) break;
                        state = processChar(current, true, state);
                        if (returnCondition(current, state)) break;
                    }
                }
                else blockMaskAndStatus.First = 0;// reset the mask block.ContentStart must follow block.ContentEnd without meeting Run in between
                if (runBeforeMove != null && runBeforeMove == runAfterMove) // the pointer just before you moved and after the move must be the same Run in order to count a character move
                {
                    state = processChar(current, false, state);
                }
                if (returnCondition(current, state)) break;
            }
            //if (Dbg.IsVerboseTrace)
            //{
            //    Dbg.Trace("|" + sb + "|");
            //}
            return state;
        }

        private static PairStruct<int, bool> TryRecognizeNewLine(PairStruct<int, bool> blockMaskAndStatus, TextPointer current)
        {
            blockMaskAndStatus.Second = false;
            if (current.Parent is RichTextBox) blockMaskAndStatus.First |= 1;
            var block = current.Parent as Block;
            if (block == null) return blockMaskAndStatus;
            // new line is characterized by reaching top level followed by block.ContentStart
            if (current.CompareTo(block.ContentStart) == 0) blockMaskAndStatus.First |= 2;

            if ((blockMaskAndStatus.First & 3) == 3)
            {
                blockMaskAndStatus.Second = true;
                blockMaskAndStatus.First = 0;
            }
            return blockMaskAndStatus;
        }

        internal struct GetSubstring_State
        {
            internal TextPointer Initial;
            internal TextPointer Current;
            internal TextPointer Target;
            internal StringBuilder Sb;
            internal Run LastRun;
        }

        public static string GetSubstring(this TextPointer initialPointer, TextPointer targetPointer)
        {
            if (initialPointer.CompareTo(targetPointer) == 0) return String.Empty;

            var state = new GetSubstring_State { Current = initialPointer, Target = targetPointer, Sb = new StringBuilder() };

            var proposedSign = state.Current.CompareTo(state.Target);
            if (proposedSign > 0)
            {
                var temp = state.Current;
                state.Current = state.Target;
                state.Target = temp;
            }
            state.Initial = state.Current;

            state = state.Initial.FoldCharacters(state,
                                            s => -1, // define direction (read only in regards to state)
                                            (sign, s) => s, // initialize (read write in regards to state)
                                            (sign, dir, s, curr) => curr.GetPositionAtOffset(1, dir), // go next (read only in regards to state)
                                            (tp, isNewLine, s) => // process character (read write in regards to state)
                                            {
                                                if (isNewLine) s.Sb.Append('\n');
                                                else
                                                {
                                                    var run = (Run)tp.Parent;
                                                    if (run != s.LastRun)
                                                    {
                                                        s.LastRun = run;
                                                        s.Sb.Append(run.Text);
                                                    }
                                                    s.Current = tp;
                                                }
                                                return s;
                                            },
                                            (tp, s) => (tp.CompareTo(s.Target) != -1)); // return condition (read only in regards to state)

            if (state.Sb.Length == 0) return String.Empty;

            var offsetBeginning = !state.Initial.IsContentEnd() && state.Initial.Parent is Run
                                    ? Math.Abs(state.Initial.GetRun().ContentStart.GetCharacterOffsetFrom(state.Initial))
                                    : 0;
            var offsetEnd = !state.Target.IsContentStart() && state.Target.Parent is Run
                                    ? Math.Abs(state.Target.GetRun().ContentEnd.GetCharacterOffsetFrom(state.Target))
                                    : 0;
            var str = state.Sb.ToString();

            if (offsetBeginning == 0 && offsetEnd == 0) return str;

            return str.Substring(offsetBeginning, str.Length - (offsetEnd + offsetBeginning));
        }

        public static bool IsContentEnd(this TextPointer pointer)
        {
            if (pointer == null) return false;
            var te = pointer.Parent as TextElement;
            return te != null && (te.ElementEnd.CompareTo(pointer) == 0 || te.ContentEnd.CompareTo(pointer) == 0);
        }

        public static bool IsContentStart(this TextPointer pointer)
        {
            if (pointer == null) return false;
            var te = pointer.Parent as TextElement;
            return te != null && (te.ElementStart.CompareTo(pointer) == 0 || te.ContentStart.CompareTo(pointer) == 0);
        }

        public static string GetCurrentChar(this TextPointer pointer, RichTextBox richTextBox)
        {
            TextPointer nextPointer = pointer.GetNextInsertionPosition(LogicalDirection.Forward);
            if (nextPointer != null)
            {
                richTextBox.Selection.Select(pointer, nextPointer);
                if (richTextBox.Selection.Text.Length != 0)
                    return richTextBox.Selection.Text[0].ToString();
            }
            return null;
        }

        public static bool IsBlockContentStart(this TextPointer pointer)
        {
            return IsBlockConditionTrue(pointer, LogicalDirection.Backward, (te, p) => te.ContentStart.CompareTo(p) == 0 || te.ElementStart.CompareTo(p) == 0);
        }
        public static bool IsBlockContentEnd(this TextPointer pointer)
        {
            return IsBlockConditionTrue(pointer, LogicalDirection.Forward, (te, p) => te.ContentEnd.CompareTo(p) == 0 || te.ElementEnd.CompareTo(p) == 0);
        }
        private static bool IsBlockConditionTrue(TextPointer pointer, LogicalDirection dir, Func<TextElement, TextPointer, bool> condition)
        {
            var sign = dir == LogicalDirection.Forward ? 1 : -1;
            var inline = pointer.Parent as Inline;
            while (inline != null)
            {
                if (!condition(inline, pointer)) return false;
                pointer = pointer.GetPositionAtOffset(sign, dir);
                if (pointer == null) return false;
                inline = pointer.Parent as Inline;
            }
            var block = pointer.Parent as Block;
            return block != null && condition(block, pointer);
        }


        public static string GetDescription(this TextPointer pointer)
        {
            if (pointer == null) return "TextPointer:NULL(parent:NULL)";
            var parent = pointer.Parent;
            if (parent == null) return "TextPointer(parent:NULL)";
            var sb = new StringBuilder();
            sb.AppendFormat("TextPointer(parent:{0})", parent.GetType().Name);
            var textElement = parent as TextElement;
            if (textElement != null)
            {
                if (textElement.ElementStart.CompareTo(pointer) == 0) sb.Append(" ElementStart");
                else if (textElement.ContentStart.CompareTo(pointer) == 0) sb.Append(" ContentStart");
                else if (textElement.ContentEnd.CompareTo(pointer) == 0) sb.Append(" ContentEnd");
                else if (textElement.ElementEnd.CompareTo(pointer) == 0) sb.Append(" ElementEnd");

                var run = textElement as Run;
                if (run != null)
                {
                    var offset = 0;
                    var start = run.ContentStart;
                    while (start != null && start.CompareTo(pointer) < 0)
                    {
                        offset++;
                        start = start.GetPositionAtOffset(1, LogicalDirection.Forward);
                    }
                    sb.AppendFormat(" text:'{0}'", InsertSubstring(run.Text, offset, "*"));
                }
            }
            return sb.ToString();
        }

        private static string InsertSubstring(string text, int offset, string substring)
        {
            if (String.IsNullOrEmpty(text) || offset < 0 || offset > text.Length) return text;
            return text.Substring(0, offset) + substring + text.Substring(offset);
        }

        public static Run GetRun(this TextPointer pointer)
        {
            return pointer.Parent as Run;
        }

        public static Paragraph GetParagraph(this TextPointer pointer)
        {
            var currentPointer = pointer;
            TextElement element;
            while ((element = currentPointer.Parent as TextElement) != null)
            {
                var paragraph = element as Paragraph;
                if (paragraph != null)
                {
                    return paragraph;
                }
                currentPointer = element.ElementEnd;
            }
            return null;
        }
    }
}